package es.unileon.is.finalproyect.users;

import java.sql.*;
import java.util.*;
import java.util.Date;

import es.unileon.is.finalproyect.auxiliar.Streams;
import es.unileon.is.finalproyect.dbconnection.ApplicationConnection;
import es.unileon.is.finalproyect.exceptions.ApplicationException;
import es.unileon.is.finalproyect.exceptions.CheckException;
import es.unileon.is.finalproyect.exceptions.NotInDatabaseException;
import es.unileon.is.finalproyect.exceptions.UniqueException;
import es.unileon.is.finalproyect.texts.*;


public class User {
	
	private Date _birth;
	private int _correctAnswers;
	private String _email;
	private String _name;
	private byte[] _picture;
	private char _sex;
	private String _surname;
	private String _type;
	private int _userID;
	private String _username;
	private int _wrongAnswers;

	public int getID(){
		return _userID;
	}
	
	public int getCorrectAnswers(){
		return _correctAnswers;
	}
	
	//comrpuebo que sea mayor que 0
	public boolean setCorrectAnswers(int answers){
		if (answers >= 0){
			try {
				ApplicationConnection.getConnection().prepareStatement("UPDATE USERS SET correctAnswers = "+answers+" WHERE userID = "+_userID).execute();
				_correctAnswers=answers;
				return true;
			} catch (SQLException e) {
				throw new RuntimeException(e.getMessage());
			}
		} else {
			return false;
		}
	}
	
	public String getEmail(){
		return new String(_email);
	}
	
	//Estaria bien comprobar que es correcto
	public boolean setEmail(String email) throws ApplicationException{
		try {
			ApplicationConnection.getConnection().prepareStatement("UPDATE USERS SET email = '"+email+"' WHERE userID = "+_userID).execute();
			_email=email;
			return true;
		} catch (SQLException e) {
			String msg = e.getMessage();
			if(msg.contains("UK2_USERS")){
				throw new UniqueException("That email is used by other user. Write other one");
			} else if(msg.contains("CK2_USERS")){
				throw new CheckException("That email is incorrect. Please write a correct one");
			} else {
				return false;
			}
		}
	}
	
	public String getName(){
		return new String(_name);
	}
	
	public void setName(String name){
		try {
			ApplicationConnection.getConnection().prepareStatement("UPDATE USERS SET name = '"+name+"' WHERE userID = "+_userID).execute();
			_name=name;
		} catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
	}
	
	public byte[] getPicture(){	
		if(_picture != null){
			return Arrays.copyOf(_picture, _picture.length);
		} else {
			return null;
		}
	}
	
	public void setPicture(byte[] picture){
		try {
			PreparedStatement ps = ApplicationConnection.getConnection().prepareStatement("UPDATE USERS SET picture = ? WHERE userID = " + _userID);
			ps.setBinaryStream(1, Streams.byteArrayToInputStream(picture));
			ps.execute();
			_picture = Arrays.copyOf(picture, picture.length);
		} catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
	}
	
	public Date getBirth(){
		return _birth;
	}
	
	public void setBirth(Date birth){
		try{
			PreparedStatement ps= ApplicationConnection.getConnection().prepareStatement("UPDATE USERS SET birthDate = ? WHERE userID = ?");
			ps.setDate(1, new java.sql.Date(birth.getTime()));
			ps.setInt(2, _userID);
			_birth = birth;
		}catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
	}
	
	public char getSex(){
		return _sex;
	}
	
	public boolean setSex(char sex){
		if((sex == 'M') || (sex == 'F')){
			try{
				ApplicationConnection.getConnection().prepareStatement("UPDATE USERS SET sex = '"+sex+"' WHERE userID = "+_userID).execute();
				_sex = sex;
				return true;
			}catch (SQLException e) {
				throw new RuntimeException(e.getMessage());
			}
		} else {
			return false;
		}
	}
	
	public String getSurname(){
		return _surname;
	}
	
	public void setSurname(String surname){
		try{
			ApplicationConnection.getConnection().prepareStatement("UPDATE USERS SET surname = '"+surname+"' WHERE userID = "+_userID).execute();
			_surname = surname;
		}catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
	}
	
	public String getUserName(){
		return _username;
	}
	
	
	public int getWrongAnswers(){
		return _wrongAnswers;
	}
	
	public boolean setWrongAnswers(int wrongAnswers){
		if(wrongAnswers >= 0){
			try{
				ApplicationConnection.getConnection().prepareStatement("UPDATE USERS SET wrongAnswers = '"+wrongAnswers+"' WHERE userID = "+_userID).execute();
				_wrongAnswers = wrongAnswers;
				return true;
			}catch (SQLException e) {
				throw new RuntimeException(e.getMessage());
			}
		} else {
			return false;
		}
	}
	
	public String getType(){
		return _type;
	}
	
	public List<Group> getAssignedGroups() {
		try{
			ResultSet rs;
			List<Group> list = new ArrayList<Group>();
			rs = ApplicationConnection.getConnection().prepareStatement("SELECT groupID FROM MEMBERS WHERE userID ="+ _userID).executeQuery();
			while(rs.next()){
				list.add(Group.requestGroup(rs.getInt(1)));
			}
			return list;
		}catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	//AQUI HAY QUE COMPROBAR ANTES QUE ESE TEXTO NO ESTE YA COMO PENDIENTE O COMO REALIZADO, Y ENTONCES SE METE  (comprobacion implementada)
	public boolean addPendingText (User admin, Text t){
		if(!admin.isAdministrator()){
			throw new RuntimeException("You do not have enough privileges to perform this operation");
		}
		int textNumber = t.getID();
		List<Integer> pending = getPendingTextsID();
		if(!pending.contains(Integer.valueOf(t.getID()))){
			List<Integer> resolved = getResolvedTextsID();
			if(!resolved.contains(t.getID())){
				try{
					ApplicationConnection.getConnection().prepareStatement("INSERT INTO PENDING_TEXTS (USERID,TEXTNUMBER) VALUES ("+_userID+","+textNumber+")").execute();
					return true;
				} catch (SQLException e){
					throw new RuntimeException(e.getMessage());
				}
			} else {
				return false;
			}
		} else {
			return false;
		}

	}
	
	//EL TIPO VIENE A SER "ADMINISTRATOR", "PENDING" y "REGULAR", solo los administradores pueden cambiar el tipo
	//DE AHI EL BOOLEANO, Si changer es administrador, entonces true y cambias el tipo, si no, false
	public boolean changeType (User changer, String newType){
		String type = changer.getType();
		String changeType = newType;
		try {
			if(type.equals("ADMINISTRATOR")){
				ApplicationConnection.getConnection().prepareStatement("UPDATE USERS SET TYPE = '"+changeType+"' WHERE userID = "+_userID).execute();
				return true;
			}
			else
				return false;
			} catch (SQLException e) {
				throw new RuntimeException(e.getMessage());
			}
	}
	
	//TIENES QUE DEVOLVER UNA TABLA HASH CUYAS CLAVES SON LAS PREGUNTAS Y LOS VALORES SON EL NUMERO DE LA RESPUESTA (1,2,3 o 4)
	//ES HACER UNA CONSULTA
	public Map<Question,Integer> getAnswersToText (Text text){
		Map<Question, Integer> table = new HashMap<Question, Integer>();
		try {
			ResultSet rs = ApplicationConnection.getConnection().prepareStatement("SELECT questionNumber, selectedAnswer FROM ANSWERS WHERE userID = " + _userID + " AND textNumber = " + text.getID()).executeQuery();
			while(rs.next()){
				table.put(Question.requestQuestion(text.getID(), rs.getInt(1)), rs.getInt(2));
			}
		} catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
		return table;

	}

	public List<Text> getPendingTexts(){
		List<Text> list = new ArrayList<Text>();
		ResultSet rs;
		try{
			rs = ApplicationConnection.getConnection().prepareStatement("SELECT textNumber FROM PENDING_TEXTS WHERE userID = " + _userID).executeQuery();
			while(rs.next()){
				list.add(Text.requestText(rs.getInt(1)));
			}
		} catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
		return list;
	}

	public List<Integer> getPendingTextsID(){
		List<Integer> list = new ArrayList<Integer>();
		ResultSet rs;
		try{
			rs = ApplicationConnection.getConnection().prepareStatement("SELECT textNumber FROM PENDING_TEXTS WHERE userID ="+ _userID).executeQuery();
			while(rs.next()){
				list.add(rs.getInt(1)); 
			}
		}catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
		return list;
	}
	
	public List<User> getPendingUsers(){
		if(!isAdministrator()){
			throw new RuntimeException("You do not have enough privileges to perform this operation");
		}
		List<User> list = new ArrayList<User>();
		ResultSet rs;
		try {
			rs = ApplicationConnection.getConnection().prepareStatement("SELECT pendingID FROM PENDING_USERS").executeQuery();
			while(rs.next()){
				list.add(User.requestUser(rs.getInt(1)));
			}
		} catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
		return list;
	}
	
	public List<Text> getResolvedTexts(){
		List<Text> list = new ArrayList<Text>();
		ResultSet rs;
		try {
			rs = ApplicationConnection.getConnection().prepareStatement("SELECT textNumber FROM RESOLVED_TEXTS WHERE userID ="+_userID).executeQuery();
			while(rs.next()){
				list.add(Text.requestText(rs.getInt(1)));
			}
		} catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
		return list;
	}
	
	public List<Integer> getResolvedTextsID(){
		List<Integer> list = new ArrayList<Integer>();
		ResultSet rs;
		try {
			rs = ApplicationConnection.getConnection().prepareStatement("SELECT textNumber FROM RESOLVED_TEXTS WHERE userID ="+_userID).executeQuery();
			while(rs.next()){
				list.add(rs.getInt(1));
			}
		} catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
		return list;
	}
	
	//Ya tienes el tipo como un atributo del objeto (HECHO¿?)
	public boolean isAdministrator(){
		String admin = "ADMINISTRATOR";
		return _type.equals(admin);
	}
	

	public boolean isPending(){
		String pending = "PENDING";
		return _type.equals(pending);
	}
	

	public boolean isRegular(){
		String regular = "REGULAR";
		return _type.equals(regular);
	}
	

	public static User requestUser(int id){
		try{
			return new User(id);
		} catch (NotInDatabaseException e){
			return null;
		}
	}
	

	public static User requestUser(String username, String password){
		try{
			return new User(username, password);
		} catch (NotInDatabaseException e){
			return null;
		}
	}
	
	//M�todo a�adido por hector (revisar) para que devuelva todos los usuarios existentes en la BD.
	public List<User> requestAllAceptedUsers(){
		if(!isAdministrator()){
			throw new RuntimeException("You do not have enough privileges to perform this operation");
		}
		List<User> list = new ArrayList<User>();
		try {			
			ResultSet rs = ApplicationConnection.getConnection().prepareStatement("SELECT regularID FROM REGULAR_USERS").executeQuery();
			while(rs.next()){
				list.add(requestUser(rs.getInt(1)));
			}
			rs = ApplicationConnection.getConnection().prepareStatement("SELECT adminID FROM ADMINISTRATORS").executeQuery();
			while(rs.next()){
				list.add(requestUser(rs.getInt(1)));
			}
		} catch (SQLException e) {
			throw new RuntimeException("No hay usuarios en la base de datos");
		}
		return list;
	
	}
	
	public void deleteUser(User admin){
		if(!admin.isAdministrator()){
			throw new RuntimeException("You do not have enough privileges to perform this operation");
		}
		
		try {
			ApplicationConnection.getConnection().prepareStatement("DELETE FROM USERS WHERE userID = "+_userID).execute();
		} catch (SQLException e) {
			throw new RuntimeException("Can't do delete operation");
		}
	}
	
	
	//Esto te mete el text en los resueltos, y answers son las preguntas con sus respuestas
	//la respuesta correcta siempre es la 1
	public void resolveText(Text text, Map<Question, Integer> answers){
		try {
			ApplicationConnection.getConnection().prepareStatement("DELETE FROM PENDING_TEXTS WHERE userID = " + _userID + " AND textNumber = " + text.getID()).execute();
			int mark = 0;
			for(int i :answers.values()){
				if(i == 1){
					mark++;
				}
			}
			ApplicationConnection.getConnection().prepareStatement("INSERT INTO RESOLVED_TEXTS (userID, textNumber, mark) VALUES (" + _userID + ", " + text.getID() + ", " + mark + ")").execute();
			for(Map.Entry<Question,Integer> entry : answers.entrySet()){
				PreparedStatement ps = ApplicationConnection.getConnection().prepareStatement("INSERT INTO ANSWERS (userID, textNumber, questionNumber, selectedAnswer) VALUES (?,?,?,?)");
				ps.setInt(1, _userID);
				ps.setInt(2, text.getID());
				ps.setInt(3, entry.getKey().getQuestionNumber());
				ps.setInt(4, entry.getValue());
				ps.execute();
			}
			setCorrectAnswers(_correctAnswers+mark);
			setWrongAnswers(_wrongAnswers+(10-mark));
		} catch (SQLException e) {
			throw new RuntimeException(e.getMessage());
		}
		
	}
	
	//Guarda todos los datos en la base de datos, y obten el identificador con una consulta. (OK ¿?¿? )
	public User(String username,String password, String name, String surname, Date birth, char sex, String email) throws ApplicationException{
		_username=username;
		_name=name;
		_surname=surname;
		_birth=birth;
		_sex=sex;
		_email=email;
		try {
			PreparedStatement ps = ApplicationConnection.getConnection().prepareStatement("INSERT INTO USERS (username,password, name, surname, birthDate, sex, email)  VALUES (?,encryptPassword(?),?,?,?,?,?)");
			ps.setString(1, _username);
			ps.setString(2, password);
			ps.setString(3, _name);
			ps.setString(4,_surname);
			ps.setDate(5, new java.sql.Date(_birth.getTime()));
			ps.setString(6, String.valueOf(_sex));
			ps.setString(7, _email);
			ps.execute();
			
			ResultSet rs = ApplicationConnection.getConnection().prepareStatement("SELECT userID, correctAnswers, wrongAnswers, type FROM USERS WHERE username ='"+ _username +"'").executeQuery();
			rs.next();
			_userID = rs.getInt(1);
			_correctAnswers = rs.getInt(2);
			_wrongAnswers = rs.getInt(3);
			_type = rs.getString(4);
			_picture = null;
		} catch (SQLException e) {
			String msg = e.getMessage();
			if(msg.contains("UK1_USERS")){
				throw new UniqueException("The desired username is already in use. Write other one");
			} else if(msg.contains("UK2_USERS")){
				throw new UniqueException("The email is used by other user. Please, write other one");
			} else if(msg.contains("CK1__USERS")){
				throw new CheckException("Please insert 'M' for male and 'F' for female as sex");
			} else if(msg.contains("CK2_USERS")){
				throw new CheckException("The introduced email is not a valid email. Please, insert a valid one");
			} else {
				throw new RuntimeException(e.getMessage());
			}
		}
	}
	
	private User(int id) throws NotInDatabaseException{
		_userID=id;
		ResultSet rs;
		try {
			rs = ApplicationConnection.getConnection().prepareStatement("SELECT username, name, surname, birthDate, sex, email, picture, correctAnswers, wrongAnswers, type FROM USERS WHERE userID ="+ _userID).executeQuery();
			if(rs.next()){
				_username=rs.getString(1);
				_name=rs.getString(2);
				_surname=rs.getString(3);
				java.sql.Date d = rs.getDate(4);
				if(d != null){
					_birth = new Date(d.getTime());
				} else {
					_birth = null;
				}
				_sex=rs.getString(5).charAt(0);
				_email=rs.getString(6);
				Blob b = rs.getBlob(7);
				if(b != null){
					_picture = Streams.inputStreamToByteArray(b.getBinaryStream());
				} else {
					_picture = null;
				}
				_correctAnswers=rs.getInt(8);
				_wrongAnswers=rs.getInt(9);
				_type=rs.getString(10);
			} else {
				throw new NotInDatabaseException("User doesn't exist");
			}
		} catch (SQLException e) {
			System.err.println(e.getMessage());
		}
		
	}
	
	//Este metodo sirve para hacer log-in en la aplicacion,
	//Obten todos los datos de la base de datos de ese usuario
	//Si el nombre de usuario o la contrase�a no coincide, entonces excepcion
	
	private User(String username, String password) throws NotInDatabaseException{
		if(username.equals("master") && password.equals("master")){
			_username = username;
			_userID = 0;
			_name = "master";
			_surname ="master";
			_birth = new Date(0);
			_type = "ADMINISTRATOR";
			return;
		}
		_username=username;
		try {
			ResultSet rs = ApplicationConnection.getConnection().prepareStatement("SELECT userID, name, surname, birthDate, sex, email, picture, correctAnswers, wrongAnswers, type FROM USERS WHERE username ='"+ _username +"' AND password =encryptPassword('"+password+"')").executeQuery();
			//si coincide..
			if(rs.next()){
				_userID=rs.getInt(1);
				_name=rs.getString(2);
				_surname=rs.getString(3);
				java.sql.Date d = rs.getDate(4);
				if(d != null){
					_birth = new Date(d.getTime());
				} else {
					_birth = null;
				}
				_sex=rs.getString(5).charAt(0);
				_email=rs.getString(6);
				Blob b = rs.getBlob(7);
				if(b != null){
					_picture = Streams.inputStreamToByteArray(b.getBinaryStream());
				} else {
					_picture = null;
				}
				_correctAnswers=rs.getInt(8);
				_wrongAnswers=rs.getInt(9);
				_type=rs.getString(10);
			}
			else{
				throw new NotInDatabaseException("Incorrect username or password");				
			}

		} catch (SQLException e) {
			System.err.println(e.toString());
		}
	}
	
	public int getMarkToText(Text t){
		try{
			PreparedStatement ps = ApplicationConnection.getConnection().prepareStatement("SELECT mark FROM RESOLVED_TEXTS WHERE userID = ? AND textNumber = ?");
			ps.setInt(1, _userID);
			ps.setInt(2, t.getID());
			ResultSet rs = ps.executeQuery();
			if(rs.next()){
				return rs.getInt(1);
			} else {
				return 0;
			}
		} catch (SQLException e){
			throw new RuntimeException(e.getMessage());
		}
		
	}
	
	public Date getDateOfExam(Text t){
		try{
			PreparedStatement ps = ApplicationConnection.getConnection().prepareStatement("SELECT dateOfExam FROM RESOLVED_TEXTS WHERE userID = ? AND textNumber = ?");
			ps.setInt(1, _userID);
			ps.setInt(2, t.getID());
			ResultSet rs = ps.executeQuery();
			if(rs.next()){
				return new Date(rs.getDate(1).getTime());
			} else {
				return null;
			}
		} catch (SQLException e){
			throw new RuntimeException(e.getMessage());
		}
	}
	
	public boolean changePassword(String pass,String old){
		try{
			PreparedStatement ps = ApplicationConnection.getConnection().prepareStatement("SELECT * FROM USERS WHERE userID = ? AND password = encryptPassword(?)");
			ps.setInt(1, _userID);
			ps.setString(2, old);
			ResultSet rs = ps.executeQuery();
			if(!rs.next()){
				return false;
			} else {
				ps = ApplicationConnection.getConnection().prepareStatement("UPDATE USERS SET password = encryptPassword(?) WHERE userID = ?");
				ps.setString(1, pass);
				ps.setInt(2, _userID);
				ps.execute();
				return true;
			}
		} catch (SQLException e){
			return false;
		}
	}
	
}
